﻿@using Senparc.Weixin.Sample
@{
    ViewData["Title"] = "Senparc.Weixin.MP 微信公众号模块";
}
<div class="sdk-doc">
    <div class="row" style="position:relative;">
        <div class="col-sm-2" id="leftMenu">
            <div class="row">
                @(await Html.PartialAsync("_SideTopNote"))

                <nav id="navbar_left" class="navbar navbar-light bg-light flex-column align-items-stretch p-3">
                    <a class="navbar-brand" href="#">索引</a>
                    <nav class="nav nav-pills flex-column">
                        <a class="nav-link" href="#source_code">源码</a>
                        <a class="nav-link" href="#title_install">如何安装？</a>
                        <a class="nav-link" href="#title_setting">如何使用？</a>
                        <a class="nav-link" href="#title_advance">进阶</a>
                        <a class="nav-link" href="#title_about">关于</a>
                        <nav class="nav nav-pills flex-column">
                            <a class="nav-link ms-3 my-1" href="#title_about_team">团队</a>
                            <a class="nav-link ms-3 my-1" href="#title_about_support">支持</a>
                            <a class="nav-link ms-3 my-1" href="#title_about_license">开源协议</a>
                        </nav>
                    </nav>
                </nav>
            </div>

            @(await Html.PartialAsync("_SideBottomNote"))
        </div>

        <div class="col-sm-10 bd-main" data-bs-spy="scroll" data-bs-target="#navbar_left" data-bs-offset="0" tabindex="0">

            <div class="text-center">
                <h1 id="top-title" class="display-4">Senparc.Weixin.MP 微信公众号模块</h1>

                <p class="no-text-indent"><a href="https://www.nuget.org/packages/Senparc.Weixin.MP" target="_blank">Senparc.Weixin.MP</a> 模块用于提供微信公众号的支持能力。</p>
                @(await Html.PartialAsync("_IconsPartial"))

            </div>

            <!-- Carousel -->
            <div id="banner-top" class="carousel slide" data-bs-ride="carousel">

                <!-- Indicators/dots -->
                <div class="carousel-indicators">
                    <button type="button" data-bs-target="#banner-top" data-bs-slide-to="0" class="active"></button>
                    <button type="button" data-bs-target="#banner-top" data-bs-slide-to="1"></button>
                    <button type="button" data-bs-target="#banner-top" data-bs-slide-to="2"></button>
                </div>

                <!-- The slideshow/carousel -->
                <div class="carousel-inner">
                    @(await Html.PartialAsync("_CarouselPartial"))
                </div>

                <!-- Left and right controls/icons -->
                <button class="carousel-control-prev" type="button" data-bs-target="#banner-top" data-bs-slide="prev">
                    <span class="carousel-control-prev-icon"></span>
                </button>
                <button class="carousel-control-next" type="button" data-bs-target="#banner-top" data-bs-slide="next">
                    <span class="carousel-control-next-icon"></span>
                </button>
            </div>

            <div id="main-doc">
                <div id="source_code" class="target-fix"></div>

                <partial name="_SourceCode" model='new SourceCodeViewModel("Senparc.Weixin.MP","/src/Senparc.Weixin.MP")' />
                
                <div id="title_install" class="target-fix"></div>

                <div>
                    <h3>如何安装？</h3>
                    <p>您可以直接引用 Senparc.Weixin 的源码进行开发，也可以引用已经打包完成的 dll（通过 Nuget 包，推荐），以方便随时获取官方的更新。注意：直接引用源码和引用 Nuget 包只能二选一。</p>

                    <h5>引用源码</h5>
                    <p>您可以在当前解决方案中，<strong>Libraries</strong> 目录下，将所需要引用的程序集引用（复制）到您开发环境的解决方案中，请注意需要同时引用被依赖的项目，如 <code>Senparc.Weixin</code> 项目是所有项目都需要依赖的。</p>
                    <p>当前示例项目默认就使用了直接引用源码的方式，可从 .csproj 文件中看到引用方式：</p>
                    <pre><code>&lt;ItemGroup&gt;
    &lt;ProjectReference Include="..\..\..\src\Senparc.Weixin.MP.Middleware\Senparc.Weixin.MP.Middleware.net6.csproj" /&gt;
    &lt;ProjectReference Include="..\..\..\src\Senparc.Weixin.MP.MvcExtension\Senparc.Weixin.MP.MvcExtension\Senparc.Weixin.MP.MvcExtension.net6.csproj" /&gt;
    &lt;ProjectReference Include="..\..\..\src\Senparc.Weixin.MP\Senparc.Weixin.MP\Senparc.Weixin.MP.net6.csproj" /&gt;
&lt;/ItemGroup&gt;</code></pre>

                    <h5>引用程序集（推荐）</h5>

                    <p>您可以通过 <code>Visual Studio</code>、<code>Visual Studio Code</code>、<code>dotnet 命令行</code> 等多种方式自动安装 Nuget 包。</p>
                    <nav>
                        <div class="nav nav-tabs" id="nav-tab" role="tablist">
                            <button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-vs" type="button" role="tab" aria-controls="nav-home" aria-selected="true">Visual Studio</button>
                            <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-vscode" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">Visual Studio Code</button>
                            <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-cli" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">dotnet 命令行</button>
                        </div>
                    </nav>
                    <div class="tab-content" id="nav-tabContent-install">
                        <div class="tab-pane fade show active" id="nav-vs" role="tabpanel" aria-labelledby="nav-home-tab">
                            <p>
                                在开发项目【解决方案资源管理器】中，对需要添加 Senparc.Weixin.MP 的模块点击右键，点击【管理 Nuget 程序包】，在【浏览】标签中输入 <strong>Senparc.Weixin.MP</strong>，点击右侧【安装】按钮。如下图所示：<br />
                            </p>
                            <p>
                                <figure class="figure">
                                    <img src="~/images/home-install-01.png" class="figure-img img-fluid rounded" alt="通过 Visual Studio 安装" />
                                    <figcaption class="figure-caption text-center">通过 Visual Studio 安装</figcaption>
                                </figure>
                            </p>

                        </div>
                        <div class="tab-pane fade" id="nav-vscode" role="tabpanel" aria-labelledby="nav-profile-tab">
                            <p>首先，确认已经安装好 <a href="https://code.visualstudio.com/">VS Code</a> 以及 dotnet 命令行（安装 <a href="https://dotnet.microsoft.com/en-us/download" target="_blank">.NET SDK</a> 后会自动安装）。</p>
                            <p>然后，打开解决方案或项目所在目录，按 <kbd>Ctrl</kbd>+<kbd>~</kbd>，打开终端面板：</p>
                            <figure class="figure">
                                <img src="~/images/home-install-03.png" class="figure-img img-fluid rounded" alt="通过 VS Code 安装" />
                                <figcaption class="figure-caption text-center">打开 VS Code 终端面板</figcaption>
                            </figure>

                            <p>进入需要添加 Senparc.Weixin.MP 的模块的项目的目录，输入：</p>
                            <blockquote class="blockquote">
                                <code>dotnet add package Senparc.Weixin.MP</code>
                            </blockquote>

                            <p>
                                <figure class="figure">
                                    <img src="~/images/home-install-04.png" class="figure-img img-fluid rounded" alt="通过 VS Code 安装" />
                                    <figcaption class="figure-caption text-center">安装 Senparc.Weixin.MP 模块</figcaption>
                                </figure>
                            </p>

                            <p>
                                安装完成后，可查看对应 .csproj 文件，被添加引用，如：
                            </p>
                            <pre><code>&lt;ItemGroup&gt;
    &lt;PackageReference Include="Senparc.Weixin.MP" Version="16.17.7" /&gt;
&lt;/ItemGroup&gt;</code></pre>
                        </div>
                        <div class="tab-pane fade" id="nav-cli" role="tabpanel" aria-labelledby="nav-profile-tab">
                            <p>首先，确认已经安装好 dotnet 命令行（安装 <a href="https://dotnet.microsoft.com/en-us/download" target="_blank">.NET SDK</a> 后会自动安装）。</p>
                            <p>进入需要添加 Senparc.Weixin.MP 的模块的项目的目录，输入：</p>
                            <blockquote class="blockquote">
                                <code>dotnet add package Senparc.Weixin.MP</code>
                            </blockquote>

                            <p>
                                <figure class="figure">
                                    <img src="~/images/home-install-02.png" class="figure-img img-fluid rounded" alt="通过 dotnet CLI 安装" />
                                    <figcaption class="figure-caption text-center">通过 dotnet CLI 安装</figcaption>
                                </figure>
                            </p>

                            <p>
                                安装完成后，可查看对应 .csproj 文件，被添加引用，如：
                            </p>
                            <pre><code>&lt;ItemGroup&gt;
    &lt;PackageReference Include="Senparc.Weixin.MP" Version="16.17.7" /&gt;
&lt;/ItemGroup&gt;</code></pre>
                        </div>
                    </div>
                </div>

                <div id="title_setting" class="target-fix"></div>
                <div @*class="mt-5 pt-5"*@>

                    <h3>如何使用？</h3>

                    <nav>
                        <div class="nav nav-tabs" id="nav-tab" role="tablist">
                            <button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-register" type="button" role="tab" aria-controls="nav-home" aria-selected="true">注册</button>
                            <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-messagehandler" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">MessageHandler</button>
                            <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-advanced-api" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">高级接口</button>
                            <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-jssdk" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">JSSDK</button>
                            <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-oauth" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">OAuth 2.0</button>
                            <button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-menu" type="button" role="tab" aria-controls="nav-profile" aria-selected="false">菜单设置</button>
                        </div>
                    </nav>
                    <div class="tab-content" id="nav-tabContent-setting">
                        <div class="tab-pane fade show active" id="nav-register" role="tabpanel" aria-labelledby="nav-home-tab">
                            <h5>全局注册</h5>
                            <p>所有的 Senparc.Weixin SDK 注册过程都是类似的。</p>
                            <p>
                                首先，完成所有 Senparc.Weixin SDK 的整体注册代码。在 Program.cs 中加入以下代码：
                            </p>
                            <p>
                                <figure class="figure">
                                    <img src="~/images/home-dev-register-01.png" class="figure-img img-fluid rounded" alt="注册 Senparc.Weixin" />
                                    <figcaption class="figure-caption text-center">注册 Senparc.Weixin</figcaption>
                                </figure>
                            </p>
                            <p>
                                说明：
                                <ol>
                                    <li><code>builder.Services.AddMemoryCache()</code> Senparc.Weixin 支持本机缓存、Redis、Memcached 等多种缓存策略，默认使用本机缓存，此时需要激活本地缓存。</li>
                                    <li><code>builder.Services.AddSenparcWeixinServices(builder.Configuration)</code> 用于完成 Senparc.Weixin 的注册。</li>
                                    <li><code>app.UseSenparcWeixin()</code> 方法用于配置和启用 Senparc.Weixin。</li>
                                </ol>
                            </p>
                            <p>
                                以上代码对于所有的 Senparc.Weixin 下级模块都是相同的，只需要 3 句代码。
                            </p>
                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /<cite title="Source Title">Program.cs</cite>
                                </figcaption>
                            </figure>

                            <h5>公众号注册</h5>
                            <p>在上述代码中的第 17 行委托方法中插入代码，即可完成默认公众号的注册：</p>
                            <p><code>register.RegisterMpAccount(weixinSetting, "【盛派网络小助手】公众号");</code></p>
                            <p>
                                <figure class="figure">
                                    <img src="~/images/home-dev-register-02.png" class="figure-img img-fluid rounded" alt="注册微信公众号" />
                                    <figcaption class="figure-caption text-center">注册微信公众号</figcaption>
                                </figure>
                            </p>
                            <p>其中，<code>weixinSetting</code> 的值默认来自于 <code>appsettings.json</code>：</p>
                            <p>
                                <pre><code>  "SenparcWeixinSetting": {
    "IsDebug": true,

    "Token": "#{Token}#",
    "EncodingAESKey": "#{EncodingAESKey}#",
    "WeixinAppId": "#{WeixinAppId}#",
    "WeixinAppSecret": "#{WeixinAppSecret}#"
  }</code></pre>
                            </p>
                            <p>
                                <figure class="figure">
                                    <img src="~/images/home-dev-register-03.png" class="figure-img img-fluid rounded" alt="配置参数" />
                                    <figcaption class="figure-caption text-center">配置参数</figcaption>
                                </figure>
                            </p>
                            <p>其中，<code>Token</code>、<code>EncodingAESKey</code>、<code>WeixinAppId</code> 和 <code>WeixinAppSecret</code> 对应了微信公众号后台的配置参数。</p>
                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /<cite title="Source Title">appsettings.json</cite>
                                </figcaption>
                            </figure>

                            <p>配置完成。</p>
                            <blockquote class="blockquote">
                                提示：自动注册的信息可通过 <code>Senparc.Weixin.Config.SenparcWeixinSetting</code> 获取。
                            </blockquote>
                        </div>

                        <div class="tab-pane fade" id="nav-messagehandler" role="tabpanel" aria-labelledby="nav-profile-tab">
                            <p><code>MessageHandler</code> 用于处理微信公众号对话窗口的消息。</p>
                            <p>SDK 已经为开发者准备好了所有需要的基础功能，开发者只需要创建一个自定义的子类，补充需要自定义的业务逻辑。</p>
                            <h5>自定义 MessageHandler</h5>
                            <p>当前示例中，我们给自定义的 MessageHandler 取名 <code>CustomMessageHandler</code>。</p>
                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p> CustomMessageHandler.cs 本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /MessageHandlers/ 目录<br />
                                    <cite title="Source Title">CustomMessageHandler.cs</cite> MessageHandler 主文件 + 普通消息处理<br />
                                    <cite title="Source Title">CustomMessageHandler_Events.cs</cite> MessageHandler 事件消息处理<br />
                                    <cite title="Source Title">CustomMessageContext.cs</cite> 自定义重写 DefaultMpMessageContext 上下文（可选）<br />
                                </figcaption>
                            </figure>
                            <p><code>CustomMessageHandler*.cs</code> 中所有演示的重写（<code>override</code>）方法中，只有 <code>DefaultResponseMessage()</code> 方法是必须重写的，其他所有 <code>OnXxxRequest()</code> 方法都为可选，当用户发送的消息，找不到对应重写方法时，则调用 <code>DefaultResponseMessage()</code> 方法。</p>

                            <p>MessageHandler 有两种承载方式，使其可以被外部（微信服务器）通过 URL 访问到。分别是<strong>中间件方式</strong>（推荐）和<strong> Controller 方式</strong>。两种方式所使用的 <code>CustomMessageHandler</code> 是通用的，因此可以随时切换和共存。</p>

                            <h5>中间件方式承载 MessageHandler</h5>

                            <p>中间件方式是推荐的方式，也是最简化的方式，无需创建任何新文件，只需在 <code>Program.cs</code> 文件所有 Senparc.Weixin 注册代码执行后的下方，引入中间件：</p>
                            <p>
                                <pre><code>app.UseMessageHandlerForMp("/WeixinAsync", CustomMessageHandler.GenerateMessageHandler, options =>
{
    options.AccountSettingFunc = context =>  Senparc.Weixin.Config.SenparcWeixinSetting;
});
                </code></pre>
                            </p>
                            <p>完成后，即可通过Url <strong><code>域名/WeixinAsync</code></strong> 访问 MessageHandler，设置为公众号后台的消息 URL。</p>
                            <p><a href="/WeixinAsync" target="_blank">测试 /WeixinAsync</a></p>
                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p> 本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /<cite title="Source Title">Program.cs</cite>
                                </figcaption>
                            </figure>

                            <p>更多中间件方式请参考：<a href="https://www.cnblogs.com/szw/p/Wechat-MessageHandler-Middleware.html" target="_blank">《在 .NET Core 2.0/3.0 中使用 MessageHandler 中间件》</a>（同样适用于 .NET 6.0 及以上）。</p>

                            <h5>Controller 方式承载 MessageHandler</h5>

                            <p>当中间件的方式满足不了需求时，可以使用 Controller 将执行过程“展开”，对每一步执行进行更加精确的控制或干预。</p>
                            <p>使用 Controller 方式，需要创建 2 个 Action，分别对应微信后台验证（Get 请求），以及真实消息推送（Post 请求）。本项目示例位于 WeixinController.cs 中。</p>
                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>WeixinController.cs 本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /Controllers/<cite title="Source Title">WeixinController.cs</cite>
                                </figcaption>
                            </figure>
                            <p>完成后，即可通过Url <strong><code>域名/Weixin</code></strong> 访问 MessageHandler，设置为公众号后台的消息 URL。</p>
                            <p><a href="/Weixin" target="_blank">测试 /Weixin</a></p>

                            <p>更多 Controller 方式请参考：<a href="https://www.cnblogs.com/szw/p/3414862.html" target="_blank">《了解MessageHandler》</a>（推荐使用全套异步方法）</p>
                        </div>


                        <div class="tab-pane fade" id="nav-advanced-api" role="tabpanel" aria-labelledby="nav-profile-tab">
                            <p>完成 <code>Program.cs</code> 文件中的常规注册后，即可在程序的任意地方使用高级接口。</p>
                            <blockquote class="blockquote">
                                注意：<br />
                                1、高级接口的配置和 <code>MessageHandler</code> 没有关联，两者可以独立或配合使用。<br />
                                2、SDK 内几乎所有高级接口的第一个参数同时支持传入 AppId 或 AccessToken，通常名称为 <code>appIdOrAccessToken</code>，SDK 会根据参数特征自动识别输入的是 AppId 还是 AccessToken，并做区分处理。
                            </blockquote>
                            <h5>使用 AppId 调用接口（推荐）</h5>
                            <p>例如，我们可以在任意一个方法中调用一个高级接口：</p>
                            <p>
                                <pre><code>var appId = Senparc.Weixin.Config.SenparcWeixinSetting.AppId;
var result = await Senparc.Weixin.MP.AdvancedAPIs.UserApi.GetAsync(appId);//获取关注者 OpenId 信息</code></pre>
                            </p>
                            <blockquote class="blockquote">
                                appId 参数，必须是已经经过注册的，这样即使 AccessToken 过期，SDK也会全自动处理。如果是未经过注册的 appId，则需要先获取 AccessToken，然后调用接口。
                            </blockquote>

                            <h5>使用 AccessToken 调用接口（不推荐）</h5>

                            <p>
                                <pre><code>var accessToken = Senparc.Weixin.MP.CommonApi.GetTokenAsync(appId, appSecret);//获取 AccessToken
var result = await Senparc.Weixin.MP.AdvancedAPIs.UserApi.GetAsync(accessToken);//获取关注者 OpenId 信息</code></pre>

                            </p>

                            <blockquote class="blockquote">
                                注意：使用 AccessToken 方式调用接口，无法保证当前 AccessToken 的有效性，因此建议使用前进行有效性校验，并使用 <code>try-catch</code> 方式捕获 AccessToken 不可用的异常，然后进行重试。因此直接使用 AccessToken 调用接口的方式并不推荐在常规情况下使用。
                            </blockquote>
                        </div>

                        <!-- JSSDK 开始 -->
                        <div class="tab-pane fade" id="nav-jssdk" role="tabpanel" aria-labelledby="nav-profile-tab">
                            <p>JSSDK 用于提供微信内置浏览器接口的能力，例如转发控制、调用摄像头权限（拍照、视频）、文件上传、关闭窗口、唤起扫码窗口，等等。</p>
                            <p>要在内置浏览器中只用 JSSDK，分为“服务端获取签名信息”和“网页端配置 JSSDK”两步。</p>
                            <h5>服务端获取签名信息</h5>
                            <p>后端通过 <code>JSSDKHelper.GetJsSdkUiPackageAsync()</code> 方法即可自动获取前端所需的所有 JSSDK 运行所需参数：</p>
                            <pre><code>public async Task<ActionResult> Index()
{
    var jssdkUiPackage = await JSSDKHelper.GetJsSdkUiPackageAsync(appId, appSecret, Request.AbsoluteUri());
    return View(jssdkUiPackage);
}</code></pre>
                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /Controllers/<cite title="Source Title">WeixiJSSDKnController.cs</cite>
                                </figcaption>
                            </figure>

                            <h5>网页端配置 JSSDK</h5>
                            <p>后端配置完成的参数，直接在前端 JS 中使用 <code>wx.config</code> 进行设置，例如以下代码将完成在转发网页时自定义转发消息的标题和图片：</p>
                            <pre><code>wx.config({
        debug: false, // 开启调试模式
        appId: '@@Model.AppId', // 必填，公众号的唯一标识
        timestamp: '@@Model.Timestamp', // 必填，生成签名的时间戳
        nonceStr: '@@Model.NonceStr', // 必填，生成签名的随机串
        signature: '@@Model.Signature',// 必填，签名
        jsApiList: [
                'checkJsApi',
                'onMenuShareTimeline',
                'onMenuShareAppMessage'
        ]
    });

    wx.error(function (res) {
        console.log(res);
        alert('验证失败');
    });

    wx.ready(function () {
        var url = '@(Context.Request.Scheme)://sdk.weixin.senparc.com';
        var link = url + '@(Context.Request.PathAndQuery())';
        var imgUrl = url + '/images/v2/ewm_01.png';

        //转发到朋友圈
        wx.onMenuShareTimeline({
            title: 'JSSDK朋友圈转发测试',
            link: link,
            imgUrl: imgUrl,
            success: function () {
                alert('转发成功！');
            },
            cancel: function () {
                alert('转发失败！');
            }
        });
        //转发给朋友
        wx.onMenuShareAppMessage({
            title: 'JSSDK朋友圈转发测试',
            desc: '转发给朋友',
            link: link,
            imgUrl: imgUrl,
            type: 'link',
            dataUrl: '',
            success: function () {
                alert('转发成功！');
            },
            cancel: function () {
                alert('转发失败！');
            }
        });
    });
</code></pre>
                            <blockquote class="blockquote">
                                提示：在 MVC 中传递 ViewModel 需要在 .cshtml 文件顶部定义：
                                <pre><code>@@model Senparc.Weixin.MP.Helpers.JsSdkUiPackage</code></pre>
                            </blockquote>
                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /Views/WeixinJSSDK/<cite title="Source Title">Index.cshtml</cite>
                                </figcaption>
                            </figure>
                            <p>更多设置详情请参考：<a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html" target="_blank">JS-SDK说明文档</a>。</p>
                        </div>
                        <!-- JSSDK 结束 -->
                        <!-- OAuth 2.0 开始 -->
                        <div class="tab-pane fade" id="nav-oauth" role="tabpanel" aria-labelledby="nav-profile-tab">
                            <p>当你需要在网页上获取用户的 OpenId、头像、昵称等信息的时候，就需要使用 OAuth 2.0 的方式和微信服务器通讯。</p>
                            <p>更多信息请参考官方文档：<a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#4" target="_blank">网页授权</a>。</p>
                            <p>SDK 已经封装了所有相关的过程，您只需要参考示例进行简单的 3 步配置即可。</p>
                            <h5>第一步：设置登录页面</h5>
                            <p>登录页面中需要设置官方 OAuth 2.0 的请求 URL（称为 <strong>AuthorizeUrl</strong>），并带上登录成功后的 returnUrl。</p>
                            <p>由于微信授权具有两种方式（<strong>snsapi_userinfo</strong> 和 <strong>snsapi_base</strong>，下方代码直接提供了两种获取  <strong>AuthorizeUrl</strong> 的方式：</p>
                            <pre><code>public ActionResult Index(string returnUrl)
{
    ViewData["returnUrl"] = returnUrl;

    //此页面引导用户点击授权
    ViewData["UrlUserInfo"] =
        OAuthApi.GetAuthorizeUrl(appId,
        "http://sdk.weixin.senparc.com/oauth2/UserInfoCallback?returnUrl=" + returnUrl.UrlEncode(),
        null, OAuthScope.snsapi_userinfo);//snsapi_userinfo方式回调地址

    ViewData["UrlBase"] =
        OAuthApi.GetAuthorizeUrl(appId,
        "http://sdk.weixin.senparc.com/oauth2/BaseCallback?returnUrl=" + returnUrl.UrlEncode(),
        null, OAuthScope.snsapi_base);//snsapi_base方式回调地址
    return View();
}</code></pre>
                            <p>上述 <code>returnUrl</code> 参数一般为跳转到登陆页面之前的 URL，也可以是希望用户完成授权之后跳转到的 URL。</p>
                            <blockquote class="blockquote">
                                注意：上述的网址和路径需要在公众号后台匹配成你自己服务器的地址（参考文档：<a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#4" target="_blank">网页授权</a>）。
                            </blockquote>

                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /Controllers/<cite title="Source Title">OAuth2Controller.cs</cite>
                                </figcaption>
                            </figure>

                            <h5>第二步：前端登录页面设置</h5>
                            <p>登录页面最终的功能是引导用户打开  <strong>AuthorizeUrl</strong>，可以直接使用连接的方式：</p>
                            <pre><code>&lt;!-- snsapi_userinfo方式回调地址 --&gt;
&lt;a href="@@ViewData["UrlUserInfo"]"&gt;点击这里测试snsapi_userinfo&lt;/a&gt;

&lt;!-- snsapi_base方式回调地址 --&gt;
&lt;a href="@@ViewData["UrlBase"]"&gt;点击这里测试snsapi_userinfo&lt;/a&gt;</code></pre>

                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /Views/OAuth2/<cite title="Source Title">Index.cshtml</cite>
                                </figcaption>
                            </figure>

                            <h5>第三步：配置登陆后回调页面</h5>
                            <p>授权成功后，网页将自动跳转到第一步中设置的回调 URL（<code>"http://sdk.weixin.senparc.com/oauth2/UserInfoCallback?returnUrl=" + returnUrl.UrlEncode()</code>），以 <strong>UserInfoCallback</strong> 为例：</p>
                            <pre><code>public ActionResult UserInfoCallback(string code, string returnUrl)
{
    if (string.IsNullOrEmpty(code))
    {
        return Content("您拒绝了授权！");
    }

    OAuthAccessTokenResult result = null;

    //通过，用code换取access_token
    try
    {
        result = OAuthApi.GetAccessToken(appId, appSecret, code);
    }
    catch (Exception ex)
    {
        return Content(ex.Message);
    }
    if (result.errcode != ReturnCode.请求成功)
    {
        return Content("错误：" + result.errmsg);
    }

    //下面2个数据也可以自己封装成一个类，储存在数据库中（建议结合缓存）
    //如果可以确保安全，可以将access_token存入用户的cookie中，每一个人的access_token是不一样的
    HttpContext.Session.SetString("OAuthAccessTokenStartTime", SystemTime.Now.ToString());
    HttpContext.Session.SetString("OAuthAccessToken", result.ToJson());

    //因为第一步选择的是OAuthScope.snsapi_userinfo，这里可以进一步获取用户详细信息
    try
    {
        if (!string.IsNullOrEmpty(returnUrl))
        {
            return Redirect(returnUrl);
        }

        OAuthUserInfo userInfo = OAuthApi.GetUserInfo(result.access_token, result.openid);
        return View(userInfo);
    }
    catch (ErrorJsonResultException ex)
    {
        return Content(ex.Message);
    }
}</code></pre>
                            <p>上述代码中，传入的 <code>returnUrl</code> 即第一步 <code>Index()</code> 方法中传入到 <strong>AuthorizeUrl</strong> 中的 <code>returnUrl</code>，当所有用户信息获取、保存等操作完成后，借助 <code>returnUrl</code> 跳转到登陆之前的页面，完成整个闭环的登录操作。</p>

                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /Controllers/<cite title="Source Title">OAuth2Controller.cs</cite>
                                </figcaption>
                            </figure>

                        </div>
                        <!-- OAuth 2.0 结束 -->
                        <!-- 菜单 开始 -->
                        <div class="tab-pane fade" id="nav-menu" role="tabpanel" aria-labelledby="nav-profile-tab">
                            <p>公众号菜单是公众号界面的重要元素，菜单的使用分为 <strong>设置</strong> 和 <strong>使用</strong> 两个环节。</p>
                            <h5>设置</h5>
                            <p>方法一：使用微信公众号后台界面设置（不使用开发模式），此处略。</p>
                            <p>方法二（推荐）：使用可视化编辑器（No Code）：<a href="https://sdk.weixin.senparc.com/Menu" target="_blank">点击查看介绍</a>。</p>
                            <p>方法三：使用代码进行设置（只需要执行一次，建议放在管理员后台，手动运行），如：</p>
                            <pre><code>public async Task CreateMenuAsync()
{
    ButtonGroup bg = new ButtonGroup();

    //定义一级菜单
    var subButton = new SubButton()
    {
        name = "一级菜单"
    };
    bg.button.Add(subButton);

    //下属二级菜单
    subButton.sub_button.Add(new SingleViewButton()
    {
        url = "https://book.weixin.senparc.com/book/link?code=SenparcRobotMenu",
        name = "《微信开发深度解析》"
    });
    subButton.sub_button.Add(new SingleClickButton()
    {
        key = "OneClick",
        name = "单击测试"
    });
    subButton.sub_button.Add(new SingleViewButton()
    {
        url = "https://weixin.senparc.com/",
        name = "Url跳转"
    });

    //最多可添加 3 个一级自定义菜单，每个菜单下最多 5 个子菜单

    var result = await CommonApi.CreateMenuAsync(appId, bg);
}</code></pre>

                            <h5>使用</h5>
                            <p>设置完菜单后，当客户端点击菜单时，微信服务器会自动推送响应的回调信息到消息 URL（即已经设置好的 MessageHandler 内），只需在 CustomMessageHandler 中重写（<code>override</code>）对应的方法即可。如针对上述 <strong>方法三</strong> 已经设定生效的菜单，当用户点击【单击测试】按钮时，我们可以在 CustomMessageHandler 中进行接收和处理：</p>
                            <pre><code>public override async Task<IResponseMessageBase> OnEvent_ClickRequestAsync(RequestMessageEvent_Click requestMessage)
{
    var reponseMessage = CreateResponseMessage<ResponseMessageText>();

    if (requestMessage.EventKey == "OneClick")
    {
        reponseMessage.Content = "您点击了【单击测试】按钮";
    }
    else
    {
        reponseMessage.Content = "您点击了其他事件按钮";
    }

    return reponseMessage;
}</code></pre>

                            <figure class="file">
                                <blockquote class="blockquote">
                                    <p>本项目参考文件：</p>
                                </blockquote>
                                <figcaption class="blockquote-footer">
                                    /MessageHandlers/<cite title="Source Title">CustomMessageHandler_Events.cs</cite>
                                </figcaption>
                            </figure>
                        </div>
                        <!-- 菜单 结束 -->
                    </div>


                    @(await Html.PartialAsync("_AdvancedPartial"))

                    @(await Html.PartialAsync("_AboutPartial"))
                </div>
            </div>
        </div>
    </div>
</div>